# MicroPython SSD1306 OLED driver, I2C and SPI interfaces

from micropython import const
import framebuf


# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)

gb_Font_name = ''
utf_8_to_gb2312 = {'深':0xC9EE,'圳':0xDBDA,'源':0xD4B4,'悦':0xD4C3,'科':0xBFC6,
                   '技':0xBCBC,'有':0xD3D0,'限':0xCFDE,'公':0xB9AB,'司':0xCBBE,
                   '温':0xCEC2,'湿':0xCAAA,'度':0xB6C8,'显':0xCFD4,'示':0xCABE,
                   '天':0xCCEC,'气':0xC6F8,'时':0xCAB1,'间':0xBCE4,'日':0xC8D5,'期':0xC6DA,'啊':0xB0A1}
HZFontInfo = [0xa1a1,94,0xb0a1,94,0x8140,190,0xaa40,96,0xa840,96]
HZFontAddr = [0,0x7980,0x3C780,0x6BF80,0xABB80]
#GB18030汉字分5个区，每个区对应2个元素，第一个对应的是该区的起始地址，
#第二个对应的是起始地址第二个字节最大值与最小值的差 + 1

# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
    def __init__(self, width, height, external_vcc):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.pages = self.height // 8
        self.buffer = bytearray(self.pages * self.width)
        super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB, self.width)
        self.init_display()

    def init_display(self):
        for cmd in (
            SET_DISP | 0x00,  # off
            # address setting
            SET_MEM_ADDR,
            0x00,  # horizontal
            # resolution and layout
            SET_DISP_START_LINE | 0x00,
            SET_SEG_REMAP | 0x01,  # column addr 127 mapped to SEG0
            SET_MUX_RATIO,
            self.height - 1,
            SET_COM_OUT_DIR | 0x08,  # scan from COM[N] to COM0
            SET_DISP_OFFSET,
            0x00,
            SET_COM_PIN_CFG,
            0x02 if self.width > 2 * self.height else 0x12,
            # timing and driving scheme
            SET_DISP_CLK_DIV,
            0x80,
            SET_PRECHARGE,
            0x22 if self.external_vcc else 0xF1,
            SET_VCOM_DESEL,
            0x30,  # 0.83*Vcc
            # display
            SET_CONTRAST,
            0xFF,  # maximum
            SET_ENTIRE_ON,  # output follows RAM contents
            SET_NORM_INV,  # not inverted
            # charge pump
            SET_CHARGE_PUMP,
            0x10 if self.external_vcc else 0x14,
            SET_DISP | 0x01,
        ):  # on
            self.write_cmd(cmd)
        self.fill(0)
        self.show()

    def poweroff(self):
        self.write_cmd(SET_DISP | 0x00)

    def poweron(self):
        self.write_cmd(SET_DISP | 0x01)

    def contrast(self, contrast):
        self.write_cmd(SET_CONTRAST)
        self.write_cmd(contrast)

    def invert(self, invert):
        self.write_cmd(SET_NORM_INV | (invert & 1))

    def show(self):
        x0 = 0
        x1 = self.width - 1
        if self.width == 64:
            # displays with width of 64 pixels are shifted by 32
            x0 += 32
            x1 += 32
        self.write_cmd(SET_COL_ADDR)
        self.write_cmd(x0)
        self.write_cmd(x1)
        self.write_cmd(SET_PAGE_ADDR)
        self.write_cmd(0)
        self.write_cmd(self.pages - 1)
        self.write_data(self.buffer)

    def Set_gb_font_name(self,fontname):
        global gb_Font_name
        gb_Font_name = fontname
#        print (gb_Font_name)
        
    def Draw_HZ_Font(self):
        x_start = y_start = 3
        
        H_Scan = 1
        
        if H_Scan == 1:
            for row in range(16):
                for col in range(16):
                    bit = fontlist[row * 2 + col // 8] >> (7 - (col % 8)) & 0x01
                    oled.pixel(x_start + col,y_start+row,bit)
            x_start += 18
        else :
            for col in range(16):
                for row in range(16):
                    bit = fontlist[32 + col * 2 + row // 8] >> (7 - (row % 8)) & 0x01
                    oled.pixel(x_start + col,y_start+row,bit)
            x_start += 18

    def CalcGBSection(self,incode):
        highbyte = (incode >> 8) & 0xff
        lowbyte = incode & 0xff
        section = 0xff
        if ((highbyte >= 0x81) and (lowbyte >= 0x40)):
            if (highbyte < 0xa1):
                section = 2
            elif ((highbyte >= 0xa1) and (highbyte <= 0xab) and (lowbyte >= 0xa1)):
                section = 0
            elif ((highbyte >= 0xa8) and (highbyte <= 0xa9)):
                section = 4
            elif ((highbyte >= 0xaa) and (highbyte <= 0xfe) and (lowbyte <= 0xa0)):
                section = 3
            elif ((highbyte >= 0xb0) and (highbyte <= 0xf7) and (lowbyte >= 0xa1)):
                section = 1
        return section

    def show_chinese(self,text,x_start,y_start,color):
#        global gb_Font_name
#        gb_Font_name = 'GB18030P.BIN'
        with open(gb_Font_name,'rb') as f:
            for char in text :
                gb_code = utf_8_to_gb2312[char]

                section = self.CalcGBSection(gb_code)
                gb_code -= HZFontInfo[section * 2]
                hbyte = (gb_code >> 8) & 0xff
                lbyte = gb_code & 0xff
                if ((section >= 2) and (lbyte > (0x7e - 0x40))):
                    lbyte -= 1
                offset = HZFontInfo[section * 2 + 1]
                offset = (offset * hbyte + lbyte) * 32
                offset += HZFontAddr[section]
                f.seek(offset)
                font_data = f.read(32)
                
                for col in range(16):
                    for row in range(16):
                        bit = font_data[col * 2 + row // 8] >> (7 - (row % 8)) & 0x01
                        self.pixel(x_start + col,y_start+row,bit)
                x_start += 18


class SSD1306_I2C(SSD1306):
    def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
        self.i2c = i2c
        self.addr = addr
        self.temp = bytearray(2)
        self.write_list = [b"\x40", None]  # Co=0, D/C#=1
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.temp[0] = 0x80  # Co=1, D/C#=0
        self.temp[1] = cmd
        self.i2c.writeto(self.addr, self.temp)

    def write_data(self, buf):
        self.write_list[1] = buf
        self.i2c.writevto(self.addr, self.write_list)


class SSD1306_SPI(SSD1306):
    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
        self.rate = 10 * 1024 * 1024
        dc.init(dc.OUT, value=0)
        res.init(res.OUT, value=0)
        cs.init(cs.OUT, value=1)
        self.spi = spi
        self.dc = dc
        self.res = res
        self.cs = cs
        import time

        self.res(1)
        time.sleep_ms(1)
        self.res(0)
        time.sleep_ms(10)
        self.res(1)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(buf)
        self.cs(1)
